# frozen_string_literal: true

module Types
  class VulnerabilityType < BaseObject
    graphql_name 'Vulnerability'
    description 'Represents a vulnerability'

    implements(Types::Notes::NoteableInterface)

    authorize :read_vulnerability

    expose_permissions Types::PermissionTypes::Vulnerability

    field :id, GraphQL::Types::ID,
      null: false, description: 'GraphQL ID of the vulnerability.'

    field :title, GraphQL::Types::String,
      null: true, description: 'Title of the vulnerability.'

    field :description, GraphQL::Types::String,
      null: true, description: 'Description of the vulnerability.'

    markdown_field :description_html, null: true

    field :message, GraphQL::Types::String,
      null: true,
      deprecated: { reason: 'message field has been removed from security reports schema', milestone: '16.1' },
      description: "From 16.1 this field always returns null."

    field :state, VulnerabilityStateEnum,
      null: true, description: "State of the vulnerability (#{::Vulnerability.states.keys.join(', ').upcase})"

    field :severity, VulnerabilitySeverityEnum,
      null: true, description: "Severity of the vulnerability (#{::Enums::Vulnerability.severity_levels.keys.join(', ').upcase})"

    field :report_type, VulnerabilityReportTypeEnum,
      null: true, description: "Type of the security report that found the vulnerability (#{::Enums::Vulnerability.report_types.keys.join(', ').upcase}). `Scan Type` in the UI."

    field :resolved_on_default_branch, GraphQL::Types::Boolean,
      null: false, description: "Indicates whether the vulnerability is fixed on the default branch or not."

    field :user_notes_count, GraphQL::Types::Int,
      null: false, description: 'Number of user notes attached to the vulnerability.'

    field :vulnerability_path, GraphQL::Types::String,
      null: true, description: "Path to the vulnerability's details page."

    field :issue_links, ::Types::Vulnerability::IssueLinkType.connection_type,
      null: false, resolver: Resolvers::Vulnerabilities::IssueLinksResolver,
      description: "List of issue links related to the vulnerability."

    field :external_issue_links, ::Types::Vulnerability::ExternalIssueLinkType.connection_type,
      null: false,
      description: 'List of external issue links related to the vulnerability.'

    field :links, [::Types::Vulnerabilities::LinkType],
      null: false, description: 'List of links associated with the vulnerability.'

    field :location, VulnerabilityLocationType,
      null: true, description: 'Location metadata for the vulnerability. Its fields depend on the type of security scan that found the vulnerability.'

    field :scanner, VulnerabilityScannerType,
      null: true, description: 'Scanner metadata for the vulnerability.'

    field :primary_identifier, VulnerabilityIdentifierType,
      null: true, description: 'Primary identifier of the vulnerability.'

    field :identifiers, [VulnerabilityIdentifierType],
      null: false, description: 'Identifiers of the vulnerability.'

    field :project, ::Types::ProjectType,
      null: true, authorize: :read_project, description: 'Project on which the vulnerability was found.'

    field :detected_at, Types::TimeType,
      null: false, method: :created_at, description: 'Timestamp of when the vulnerability was first detected.'

    field :confirmed_at, Types::TimeType,
      null: true, description: 'Timestamp of when the vulnerability state was changed to confirmed.'

    field :resolved_at, Types::TimeType,
      null: true, description: 'Timestamp of when the vulnerability state was changed to resolved.'

    field :dismissed_at, Types::TimeType,
      null: true, description: 'Timestamp of when the vulnerability state was changed to dismissed.'

    field :updated_at, Types::TimeType,
      null: true, description: 'Timestamp of when the vulnerability was last updated.'

    field :has_solutions, GraphQL::Types::Boolean,
      null: true, resolver_method: :has_solutions?,
      description: 'Indicates whether there is a solution available for this vulnerability.'

    field :state_comment, GraphQL::Types::String,
      null: true, description: 'Comment given for the vulnerability state change.'

    field :merge_request, ::Types::MergeRequestType,
      null: true, description: 'Merge request that fixes the vulnerability.'

    field :confirmed_by, ::Types::UserType,
      null: true, description: 'User that confirmed the vulnerability.'

    field :resolved_by, ::Types::UserType,
      null: true, description: 'User that resolved the vulnerability.'

    field :dismissed_by, ::Types::UserType,
      null: true, description: 'User that dismissed the vulnerability.'

    field :details, [VulnerabilityDetailType],
      null: false,
      resolver: Resolvers::Vulnerabilities::DetailsResolver,
      description: 'Details of the vulnerability.'

    field :false_positive, GraphQL::Types::Boolean,
      null: true, resolver_method: :false_positive?,
      description: 'Indicates whether the vulnerability is a false positive.'

    field :web_url, GraphQL::Types::String,
      null: true, description: "URL to the vulnerability's details page."

    field :state_transitions, ::Types::Vulnerability::StateTransitionType.connection_type,
      null: true,
      description: "List of state transitions related to the vulnerability."

    def confirmed_by
      ::Gitlab::Graphql::Loaders::BatchModelLoader.new(::User, object.confirmed_by_id).find
    end

    def resolved_by
      ::Gitlab::Graphql::Loaders::BatchModelLoader.new(::User, object.resolved_by_id).find
    end

    def dismissed_by
      ::Gitlab::Graphql::Loaders::BatchModelLoader.new(::User, object.dismissed_by_id).find
    end

    def user_notes_count
      ::Gitlab::Graphql::Aggregations::Vulnerabilities::LazyUserNotesCountAggregate.new(context, object)
    end

    def vulnerability_path
      ::Gitlab::Routing.url_helpers.project_security_vulnerability_path(object.project, object)
    end

    def web_url
      ::Gitlab::Routing.url_helpers.project_security_vulnerability_url(object.project, object)
    end

    def location
      object_location = object.finding&.location
      object_location&.merge(blob_path: object.blob_path, report_type: object.report_type)&.compact
    end

    def scanner
      Representation::VulnerabilityScannerEntry.new(object.finding&.scanner, object.report_type)
    end

    def primary_identifier
      object.finding&.primary_identifier
    end

    def identifiers
      object.finding&.identifiers
    end

    def description
      object.description || object.finding_description
    end

    def description_html_resolver
      ::MarkupHelper.markdown(description, context.to_h.dup)
    end

    def project
      Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, object.project_id).find
    end

    def state_comment
      if Feature.enabled?(:deprecate_vulnerabilities_feedback, object.project)
        object.state_transitions.last&.comment
      else
        object.finding&.dismissal_feedback&.comment
      end
    end

    def merge_request
      if Feature.enabled?(:deprecate_vulnerabilities_feedback, object.project)
        object.merge_requests.first
      else
        object.finding&.merge_request_feedback&.merge_request
      end
    end

    def has_solutions?
      object.finding&.remediations&.any?
    end

    def false_positive?
      return unless expose_false_positive?

      object.finding&.false_positive? || false
    end

    # Remove with field :message; https://gitlab.com/gitlab-org/gitlab/-/issues/412114
    def message
      # no-op
    end

    private

    def expose_false_positive?
      object.project.licensed_feature_available?(:sast_fp_reduction)
    end
  end
end
